/*******************************************************************************
 * Copyright 2011-2012 Dik Grapendaal
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package sh.grapendaal.tsuushin.service.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

import sh.grapendaal.tsuushin.core.BotFilter;
import sh.grapendaal.tsuushin.core.IrcBot;
import sh.grapendaal.tsuushin.core.Network;
import sh.grapendaal.tsuushin.service.BotService;

@Service
public class BotServiceImpl implements ApplicationContextAware, BotService {
	private static final Logger logger = LoggerFactory.getLogger(BotServiceImpl.class);

	private ApplicationContext applicationContext;
	private ScheduledThreadPoolExecutor executor;
	private ScheduledFuture<?> scheduledFuture;
	private List<IrcBot> connectedBots = new ArrayList<IrcBot>();
	private List<IrcBot> unconnectedBots = new ArrayList<IrcBot>();

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public IrcBot createBot(String nickname, Network network) {
		IrcBot bot = applicationContext.getBean(IrcBot.class);
		synchronized (unconnectedBots) {
			unconnectedBots.add(bot);
		}

		bot.setNickname(nickname);
		bot.setNetwork(network);

		return bot;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void connectBots() {
		for (IrcBot bot : unconnectedBots) {
			new Thread(bot).start();
		}

		synchronized (unconnectedBots) {
			connectedBots.addAll(unconnectedBots);
			unconnectedBots.clear();
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void scheduleReconnect(IrcBot bot) {
		logger.debug("Scheduling reconnect for bot {}", bot);
		synchronized (unconnectedBots) {
			connectedBots.remove(bot);
			unconnectedBots.add(bot);
		}

		if (scheduledFuture.isDone()) {
			scheduledFuture = executor.schedule(new ConnectBotsTask(), 10L, TimeUnit.SECONDS);
			logger.debug("Scheduled new ConnectBotsTask.");
		} else {
			logger.debug("ConnectBotsTask is already scheduled.");
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public BotFilter filter() {
		return new BotFilter(connectedBots);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void start() throws Exception {
		if (executor == null) {
			executor = new ScheduledThreadPoolExecutor(1);
			scheduledFuture = executor.schedule(new ConnectBotsTask(), 3L, TimeUnit.SECONDS);
		} else {
			throw new Exception("Bot service is already running.");
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void stop() {
		for (IrcBot bot : connectedBots) {
			bot.stop();
		}

		if (executor != null) {
			executor.shutdown();
			executor = null;
		}
	}

	/**
	 * This task will call the connectBots() method on the Bot Service, which
	 * will reconnect any unconnected bots.
	 */
	private class ConnectBotsTask implements Runnable {
		@Override
		public void run() {
			try {
				BotServiceImpl.this.connectBots();
			} catch (Exception e) {
				logger.error("Exception during ConnectBotsTask", e);
			}
		}
	}
}
